library(igraph)
##
## Attaching package: 'igraph'
## The following objects are masked from 'package:stats':
##
## decompose, spectrum
## The following object is masked from 'package:base':
##
## union
library(igraphdata)
library(visNetwork)
data(package = "igraphdata")
data(foodwebs)
Why igraph?
Pretty fast for most things
Many other pachages use it
There are a ton of companion packages. If you want to do something you are working with using igraph, and it doesnt seem like igraph can do it, or igraph does it poorly, chances are theres a package which lets you do it/ do it better better using igraph formats
Versions exist in R, Python, Matlab, C/C++ so data and pseudocode can be easily transferred between languages
A representation of individuals/components of a system and their connections which is defined in a way that allows us to analyze them mathematically and computationally
Vertices/nodes/actors : Individuals/components
Edges/ links/ ties : connections between individuals/components
A “graph” in mathematics is simply a set of vertices paired with the corresponding set of edges. A “network” is that, in addition to the other variables and properties associated with the vertices and edges
Examples:
g<-make_empty_graph( directed = FALSE)
g <- g + vertices('Bio', 'Eco', 'CS', 'Soc', 'Psych', 'Econ')
plot(g, vertex.label.dist=3) #Add a little label distance so the labels dont overlap with the nodes. Easier to see
Adding edges is simple
g <- g + edges(c('CS','Bio', 'CS','Eco', 'CS','Econ', 'CS','Soc', 'CS','Psych'))
plot(g, vertex.label.dist=3)
You dont have to add all your edges at once
g <- g + edges(c('Bio','Eco', 'Eco','Econ', 'Soc','Psych', 'Soc','Econ'))
plot(g, vertex.label.dist=3)
If you do not have any isolated nodes sitting unconnected, then just entering the list of connections is enough, without explicitly stating the nodes
edgeList <- matrix( c('CS','Bio', 'CS','Eco', 'CS','Econ', 'CS','Soc', 'CS','Psych', 'Bio','Eco', 'Eco','Econ', 'Soc','Psych', 'Soc','Econ' ), nc = 2, byrow = TRUE)
print(edgeList)
## [,1] [,2]
## [1,] "CS" "Bio"
## [2,] "CS" "Eco"
## [3,] "CS" "Econ"
## [4,] "CS" "Soc"
## [5,] "CS" "Psych"
## [6,] "Bio" "Eco"
## [7,] "Eco" "Econ"
## [8,] "Soc" "Psych"
## [9,] "Soc" "Econ"
edgeList2 <- as_edgelist(g, names = TRUE)
print(edgeList2)
## [,1] [,2]
## [1,] "Bio" "CS"
## [2,] "Eco" "CS"
## [3,] "CS" "Econ"
## [4,] "CS" "Soc"
## [5,] "CS" "Psych"
## [6,] "Bio" "Eco"
## [7,] "Eco" "Econ"
## [8,] "Soc" "Psych"
## [9,] "Soc" "Econ"
gEdgeList<-graph_from_edgelist(edgeList, directed=FALSE)
plot(gEdgeList, vertex.label.dist=3)
gEdgeList2<-graph_from_edgelist(edgeList2, directed=FALSE)
plot(gEdgeList2, vertex.label.dist=3)
You can imagine how this would be useful if your data came in the form of a list of edges, as mine has in the past.
adjMatG <- as_adjacency_matrix(g) #get the adjacency matrix of g
print(adjMatG)
## 6 x 6 sparse Matrix of class "dgCMatrix"
## Bio Eco CS Soc Psych Econ
## Bio . 1 1 . . .
## Eco 1 . 1 . . 1
## CS 1 1 . 1 1 1
## Soc . . 1 . 1 1
## Psych . . 1 1 . .
## Econ . 1 1 1 . .
gAdjMat <- graph_from_adjacency_matrix(adjMatG, mode = "undirected") #create a new network from the adjacency matrix
plot(gAdjMat, vertex.label.dist=3)
In general, there are a million ways you can make the same network, and some are easier than others given the data you have and the format it is in. If one way is very hard, consider looking into another way.
Directed networks are networks where an a connection between two nodes points from one node to the other in a way where the direciton of the pointing is important. Often the direction implies that something flows in that direction
Examples:
Food Webs: A points to B if the biomass of A transfers to B (B eats A)
Needle-sharing network: A points to B if B uses a needle after A (pathogen could spread from A to B)
Citation Networks: A points to B if A cites B (an idea passes from A to B)
Simple example of directed network:
adjMat <-matrix(data = 0, nrow=7,ncol=7)
species <- c('coyote', 'vulture', 'snake', 'grass', 'bug', 'hawk', 'mouse')
rownames(adjMat) <- colnames(adjMat) <- species
print(adjMat)
## coyote vulture snake grass bug hawk mouse
## coyote 0 0 0 0 0 0 0
## vulture 0 0 0 0 0 0 0
## snake 0 0 0 0 0 0 0
## grass 0 0 0 0 0 0 0
## bug 0 0 0 0 0 0 0
## hawk 0 0 0 0 0 0 0
## mouse 0 0 0 0 0 0 0
adjMat["grass","bug"] <-1
adjMat["bug","hawk"] <-1
adjMat["grass","mouse"] <-1
adjMat["mouse","hawk"] <-1
adjMat["mouse","coyote"] <-1
adjMat["mouse","snake"] <-1
adjMat["mouse","vulture"] <-1
adjMat["hawk","coyote"] <-1
adjMat["coyote","vulture"] <-1
adjMat["snake","vulture"] <-1
print(adjMat)
## coyote vulture snake grass bug hawk mouse
## coyote 0 1 0 0 0 0 0
## vulture 0 0 0 0 0 0 0
## snake 0 1 0 0 0 0 0
## grass 0 0 0 0 1 0 1
## bug 0 0 0 0 0 1 0
## hawk 1 0 0 0 0 0 0
## mouse 1 1 1 0 0 1 0
foodWeb <- graph_from_adjacency_matrix(adjMat)
plot(foodWeb, vertex.label.dist=3)
is_weighted(foodWeb)
## [1] FALSE
Weighted networks are networks where edges come with “weight” which usually implies something about the “strength” or “length” of that edge.
Examples:
Transportation networks: weights may imply distance
Food web: weights can denote energy flux between species
Water transfer networks: weights can denote rate of transfer
#Check if your network is weighted
is_weighted(foodWeb)
## [1] FALSE
adjMat["grass","bug"] <-0.2
adjMat["bug","hawk"] <-1
adjMat["grass","mouse"] <-0.8
adjMat["mouse","hawk"] <-0.2
adjMat["mouse","coyote"] <-0.2
adjMat["mouse","snake"] <-0.2
adjMat["mouse","vulture"] <-0.4
adjMat["hawk","coyote"] <-1
adjMat["coyote","vulture"] <-1
adjMat["snake","vulture"] <-1
print(adjMat)
## coyote vulture snake grass bug hawk mouse
## coyote 0.0 1.0 0.0 0 0.0 0.0 0.0
## vulture 0.0 0.0 0.0 0 0.0 0.0 0.0
## snake 0.0 1.0 0.0 0 0.0 0.0 0.0
## grass 0.0 0.0 0.0 0 0.2 0.0 0.8
## bug 0.0 0.0 0.0 0 0.0 1.0 0.0
## hawk 1.0 0.0 0.0 0 0.0 0.0 0.0
## mouse 0.2 0.4 0.2 0 0.0 0.2 0.0
foodWeb <- graph_from_adjacency_matrix(adjMat, weighted=TRUE)
plot(foodWeb, vertex.label.dist=3)
#Notice that nothing looks different
#Now check if your network is weighted again
is_weighted(foodWeb)
## [1] TRUE
#Label the edges with weights
plot(foodWeb, vertex.label.dist=3, edge.label=E(foodWeb)$weight)
#Labelling edges is good when you need to know the exact values of weights, but it gets messy with big or dense networks(with alot of edges)
#Map edge weights to the widths of plotted edges
E(foodWeb)$width <- E(foodWeb)$weight
plot(foodWeb, vertex.label.dist=3)
#This is good except some edges are too thin
# Modify edge widths to make it look better
E(foodWeb)$width <- E(foodWeb)$weight*4
plot(foodWeb, vertex.label.dist=3)
fwc <- foodwebs$CrystalC
plot(fwc, layout=layout_as_tree, vertex.label.dist=1.5)
## Warning in v(graph): At structural_properties.c:3346 :graph contains a
## cycle, partial result is returned
plot(fwc, layout=layout_as_tree, vertex.label = 1:length(V(fwc)))
## Warning in v(graph): At structural_properties.c:3346 :graph contains a
## cycle, partial result is returned
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
Weight and direction are attributes of edges
Vertices also can have attributes.
So far we have used the vertex attribute “name”, but vertices can have all sorts of attributes
#Check current vertex attributes
vertex_attr_names(fwc)
## [1] "name" "ECO" "Biomass"
V(fwc)$size <- V(fwc)$Biomass #note that we are setting a new vertex attribute
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
V(fwc)$size <- log1p(V(fwc)$Biomass)
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
V(fwc)$color <- V(fwc)$ECO
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
Network analysis refers to analyzing existing networks
#Degree
V(fwc)$size <- degree(fwc)
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
V(fwc)$size <- degree(fwc, mode="in")
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
V(fwc)$size <- degree(fwc, mode="out")
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
#Betweenness Centrality
V(fwc)$size <- log1p(betweenness(fwc))*5
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
#Closeness
#Calculated as the reciprocal of the sum of the length of the shortest paths between the node and all other nodes in the graph. Thus, the more central a node is, the closer it is to all other nodes.
V(fwc)$size <- closeness(fwc)*10^4
## Warning in closeness(fwc): At centrality.c:2617 :closeness centrality is
## not well-defined for disconnected graphs
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
fwc <- foodwebs$CrystalC
fwc1 <- delete_vertices(fwc, c('Input','Output', "Respiration", 'detritus' ))
#V(fwc1)$size <-1
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned